import hashlib
import os
import random
from io import BytesIO

from PIL import ImageFilter, Image
from PIL.ImageDraw import Draw
from PIL.ImageFont import truetype
from django.template.defaultfilters import length

ALL_CHARS = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'


def gen_random_code(content):
    return ''.join(random.choice(ALL_CHARS,k=length))

def gen_md5_digest(content):
    return hashlib.md5(content.encode()).hexdigest()


def pascal_row(n=0):
    '''生成毕达哥拉斯三角形（杨辉三角）'''
    result = [1]
    x, numerator = 1, n
    for denominator in range(1, n // 2 + 1):
        x *= numerator
        x /= denominator
        result.append(x)
        numerator -= 1
    if n & 1 == 0:
        result.extend(reversed(result[:-1]))
    else:
        result.extend(reversed(result))
    return result

def random_color(start=0, end=255, opacity=255):
    '''获得随机颜色'''
    red = random.randint(start, end)
    green = random.randint(start, end)
    blue = random.randint(start, end)
    if opacity is None:
        return red, green, blue
    return red, green, blue, opacity


class Bezier:
    def __init__(self):
        self.tsequence = tuple([t / 20.0 for t in range(21)])
        self.bezier ={}
    def make_bezier(self,n):
        try:
            return self.beziers[n]
        except KeyError:
            combinations = pascal_row(n - 1)
            result = []
            for t in self.tsequence:
                tpowers = (t ** i for i in range(n))
                upowers = ((1 - t) ** i for i in range(n - 1, -1, -1))
                coefs = [c * a * b for c, a, b in zip(combinations,
                                                      tpowers, upowers)]
                result.append(coefs)
            self.beziers[n] = result
            return result

class Captcha:
    '''验证码'''
    def __init__(self,width,height,fonts=None,color=None):
        self._image = None
        self._fonts = fonts if fonts else \
            [os.path.join(os.path.dirname(__file__),'fonts',font)
             for font in ['Arial.ttf', 'Georgia.ttf', 'Action.ttf']]
        self._color = color if color else random_color(0,200,random.randint(220, 255))
        self._width,self._height = width,height

    @classmethod
    def instance(cls,width=200,height=75):
        '''用于获取captcha对象的类方法'''
        prop_name = f'_instance_{width}_{height}'
        if not hasattr(cls,prop_name):
            setattr(cls,prop_name,cls(width,height))
        return getattr(cls,prop_name)
    def _backgroud(self):
        '''绘制背景'''
        Draw(self._image).rectangle([(0,0),self._image.size]
                                    ,fill=random_color(230,255))

    def _smooth(self):
        '''平滑图像'''
        return self._image.filter(ImageFilter.SMOOTH)

    def _curve(self,width=4,number=6,color=None):
        '''绘制曲线'''
        dx,height = self._image.size
        dx /= number
        path = [(dx *i,random.randint(0,height))
                for i in range(1,number)]
        bcoefs = Bezier().make_bezier(number - 1)
        points = []
        for coefs in bcoefs:
            points.append(tuple(sum([coef * p for coef,p in zip(coefs,ps)])
                                for ps in zip(*path)))
            Draw(self._image).line(points,fill=color if color else self._color,width=width)

    def _noise(self,number = 50,level = 2,color=None):
        '''绘制扰码'''
        width,height = self._image.size
        dx,dy = width/10,height/10
        width,height = width - dx,height - dy
        draw = Draw(self._image)
        for i in range(number):
            x = int(random.uniform(dx,width))
            y = int(random.uniform(dy,height))
            draw.line(((x,y),(x + level,y)),
                      fill = color if color else self._color,width=level)

    def _text(self,captcha_text,fonts,font_size=None,drawings=None,squeeze_factor=0.75, color=None):
        '''绘制文本'''
        color = color if color else self._color
        fonts = tuple([truetype(name,size)
                       for name in fonts
                       for size in font_size or (65,70,75)])
        draw = Draw(self._image)
        char_images = []
        for c in captcha_text:
            font = random.choice(fonts)
            c_width,c_height = draw.textsize(c,font=font)
            char_image = Image.new('RGB',(c_width,c_height),(0,0,0))
            char_draw = Draw(char_image)
            char_image = char_image.crop(char_image.getbbox())
            for drawing in drawings:
                d = getattr(self,drawing)
                char_images = d(char_image)
            char_images.append(char_image)
            width,height = self._image.size
            offset = int((width - sum(int(i.size[0] * squeeze_factor)
                                      for i in char_images[:-1]) -
                          char_images[-1].size[0])/2)
            for char_image in char_images:
                c_width,c_height = char_image.size
                mask = char_image.convert('L').point(lambda i:i *1.97)
                self._image.paste(char_image,(offset,int((height - c_height)/2)),mask)
                offset += int(c_width*squeeze_factor)

    @staticmethod
    def _warp(image,dx_factor=0.3,dy_factor=0.3):
        '''图像扭曲'''
        width,height = image.size
        dx = width * dx_factor
        dy = height * dy_factor
        x1 = int(random.uniform(-dx,dx))
        y1 = int(random.uniform(-dy,dy))
        x2 = int(random.uniform(-dx,dx))
        y2 = int(random.uniform(-dy,dy))
        warp_image = Image.new('RGB',
                               (width+abs(x1)+abs(x2),height+abs(y1)+abs(y2)))
        warp_image.paste(image,(abs(x1),abs(y1)))
        width2,height2 = warp_image.size
        return warp_image.transform(
            (width,height),
            Image.QUAD,
            (x1,y1,-x1,height2-y2,width2+x2,height2+y2,width2-x2,-y1))

    @staticmethod
    def _offset(image,dx_factor=0.1,dy_factor=0.2):
        '''图像偏移'''
        width,height = image.size
        dx = int(random.random() * width * dx_factor)
        dy = int(random*random() * height * dy_factor)
        offset_image = Image.new('RGB',(width + dx, height + dy))
        offset_image.paste(image,(dx,dy))
        return offset_image

    @staticmethod
    def _rotate(image,angle=25):
        '''图像旋转'''
        return image.rotate(random.uniform(-angle,angle),Image.BILINEAR,expand=1)

    def generate(self,captcha_text='',fmt='PNG'):
        '''生成验证码（图片和文字）'''
        self._image = Image.new('RGB',self._width,self._height,(255,255,255))
        self._backgroud()
        self._text(captcha_text,self._fonts,drawings=['_warp','_rotate','_'])
        self._curve()
        self._noise()
        self._smooth()
        image_bytes = BytesIO()
        self._image.save(image_bytes,format=fmt)
        return image_bytes.getvalue()
















